﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls.Primitives;
using System.Linq;

namespace SStuff.FlowControls
{

    public abstract class FlowPanelBase : VirtualizingPanel
    {
        private double _layoutOffset;
        private int _itemCount;
        private List<UIElement> _realizedChildren;
        private int _firstVisibleIndex, _lastVisibleIndex;

        public double LayoutOffset
        {
            get { return _layoutOffset; }
            set
            {
                _layoutOffset = value;
                InvalidateMeasure();
            }
        }

        private bool _cyclicMode;

        public bool CyclicMode
        {
            get { return _cyclicMode; }
            set { _cyclicMode = value;
            InvalidateMeasure();
            }
        }



        public int ItemCount
        {
            get { return _itemCount; }
            set
            {
                _itemCount = value;
                InvalidateMeasure();
            }
        }

        private double _consideredItems;

        public double ConsideredItems
        {
            get { return _consideredItems; }
            set { _consideredItems = value; }
        }

        protected abstract void SendAvailableSizeToAlgorithm(Size availableSize);


        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {

            if (double.IsPositiveInfinity(availableSize.Width) || double.IsPositiveInfinity(availableSize.Height))
            {
                Debug.WriteLine("The container should have a restricted size");
                return new Size(0, 0);
            }

            SendAvailableSizeToAlgorithm(availableSize);

            EnsureRealizedChildren();
            CalculateIndices();
            RecycleContainers();

            foreach (var range in GetVisibleRanges())
            {
                EnsureChildrenCreated(range, availableSize);
            }
            DisconnectRecycledContainers();

            ApplyZOrder(_realizedChildren);

            return availableSize;
        }

        protected abstract void ApplyZOrder(IEnumerable<UIElement> children);

        private void DisconnectRecycledContainers()
        {
            int realizedIndex = 0;
            UIElement visualChild;
            UIElement realizedChild = _realizedChildren.Count > 0 ? _realizedChildren[0] : null;
            UIElementCollection children = Children;

            int removeStartRange = -1;
            int removalCount = 0;
            for (int i = 0; i < children.Count; i++)
            {
                visualChild = children[i];

                if (visualChild == realizedChild)
                {
                    if (removalCount > 0)
                    {
                        RemoveInternalChildRange(removeStartRange, removalCount);
                        i -= removalCount;
                        removalCount = 0;
                        removeStartRange = -1;
                    }

                    realizedIndex++;

                    if (realizedIndex < _realizedChildren.Count)
                    {
                        realizedChild = _realizedChildren[realizedIndex];
                    }
                    else
                    {
                        realizedChild = null;
                    }
                }
                else
                {
                    if (removeStartRange == -1)
                    {
                        removeStartRange = i;
                    }

                    removalCount++;
                }
            }

            if (removalCount > 0)
            {
                RemoveInternalChildRange(removeStartRange, removalCount);
            }
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            EnsureRealizedChildren();
            foreach (var child in _realizedChildren)
            {

                if (child.DesiredSize.Height == 0.0 || child.DesiredSize.Width == 0.0) continue;

                child.Arrange(new Rect(finalSize.Width * 0.5 - child.DesiredSize.Width * 0.5, finalSize.Height * 0.5 - child.DesiredSize.Height * 0.5, child.DesiredSize.Width, child.DesiredSize.Height));
            }
            return finalSize;
        }

        private void EnsureChildrenCreated(Tuple<int, int> range, Size availableSize)
        {
            GeneratorPosition start = ItemContainerGenerator.GeneratorPositionFromIndex(range.Item1);
            int childIndex = (start.Offset == 0) ? start.Index : start.Index + 1;
            if (childIndex < 0)
                childIndex = 0;
            using (ItemContainerGenerator.StartAt(start, GeneratorDirection.Forward, true))
            {
                for (int i = range.Item1; i <= range.Item2; ++i)
                {
                    bool isNewlyRealized;

                    UIElement child = ItemContainerGenerator.GenerateNext(out isNewlyRealized) as UIElement;
                    if (child == null) continue;

                    if (isNewlyRealized)
                    {
                        InsertContainer(childIndex, child, false);
                    }
                    else
                    {
                        if (childIndex >= _realizedChildren.Count || !(_realizedChildren[childIndex] == child))
                        {
                            // we have a recycled container (if it was realized container it would have been returned in the
                            // propert location). Note also that recycled containers are NOT in the _realizedChildren list.
                            InsertContainer(childIndex, child, true);
                        }
                        else
                        {
                            // previously realized child, so do nothing
                        }
                    }
                    childIndex++;
                    child.Measure(availableSize);

                    int itemIndex = ItemsControl.GetItemsOwner(this).ItemContainerGenerator.IndexFromContainer(child);
                    double relativeItemIndex = CalculateRelativeIndex(itemIndex);

                    ApplyLayout(relativeItemIndex, child, (int)_consideredItems);
                }
            }
        }

        protected abstract void ApplyLayout(double normalizedIndex, UIElement element, int consideredItemsCount);

        double CalculateRelativeIndex(int itemIndex)
        {
            double relativeIndex = itemIndex - _layoutOffset;
            if (relativeIndex < 0)
                relativeIndex += _itemCount;
            if (Math.Abs(relativeIndex - _itemCount) < Math.Abs(relativeIndex))
                relativeIndex = relativeIndex - ItemCount;
            return relativeIndex * 2.0 / _consideredItems;
        }
        private void InsertContainer(int childIndex, UIElement container, bool isRecycled)
        {
            // index in Children collection, whereas childIndex is the index into the _realizedChildren collection
            int visualTreeIndex = 0;
            UIElementCollection children = Children;

            if (childIndex > 0)
            {
                // find the item before where we want to insert the new item
                visualTreeIndex = ChildIndexFromRealizedIndex(childIndex - 1);
                visualTreeIndex++;
            }

            if (isRecycled && visualTreeIndex < children.Count && children[visualTreeIndex] == container)
            {
                // don't insert if a recycled container is in the proper place already
            }
            else
            {
                if (visualTreeIndex < children.Count)
                {
                    int insertIndex = visualTreeIndex;
                    if (isRecycled && VisualTreeHelper.GetParent(container) != null)
                    {
                        // If the container is recycled we have to remove it from its place in the visual tree and 
                        // insert it in the proper location.   We cant use an internal Move api, so we are removing
                        // and inserting the container
                        int containerIndex = children.IndexOf(container);
                        RemoveInternalChildRange(containerIndex, 1);
                        if (containerIndex < insertIndex)
                        {
                            insertIndex--;
                        }

                        InsertInternalChild(insertIndex, container);
                    }
                    else
                    {
                        InsertInternalChild(insertIndex, container);
                    }
                }
                else
                {
                    if (isRecycled && VisualTreeHelper.GetParent(container) != null)
                    {
                        // Recycled container is still in the tree; move it to the end
                        int originalIndex = children.IndexOf(container);
                        RemoveInternalChildRange(originalIndex, 1);
                        AddInternalChild(container);
                    }
                    else
                    {
                        AddInternalChild(container);
                    }
                }
            }

            // Keep realizedChildren in sync w/ the visual tree.
            _realizedChildren.Insert(childIndex>=0?childIndex:0, container);
            ItemContainerGenerator.PrepareItemContainer(container);
        }

        private int ChildIndexFromRealizedIndex(int realizedChildIndex)
        {
            UIElementCollection children = Children;
            // If we're not recycling containers then we're not using a realizedChild index and no translation is necessary
            if (realizedChildIndex < _realizedChildren.Count)
            {
                UIElement child = _realizedChildren[realizedChildIndex];

                for (int i = realizedChildIndex; i < children.Count; i++)
                {
                    if (children[i] == child)
                    {
                        return i;
                    }
                }
            }

            return realizedChildIndex;
        }

        private void RecycleContainers()
        {
            if (Children.Count == 0) return;

            ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
            int recycleRangeStart = -1;
            int recycleRangeCount = 0;
            int childCount = _realizedChildren.Count;
            for (int i = 0; i < childCount; i++)
            {
                bool recycleContainer = false;

                int itemIndex = itemsControl.Items.IndexOf((_realizedChildren[i] as ContentControl).Content);

                if (itemIndex >= 0 && (!IsInVisibleRange(itemIndex)))
                {
                    recycleContainer = true;
                }

                if (!Children.Contains(_realizedChildren[i]))
                {
                    recycleContainer = false;
                    _realizedChildren.RemoveRange(i, 1);
                    i--;
                    childCount--;
                }

                if (recycleContainer)
                {
                    if (recycleRangeStart == -1)
                    {
                        recycleRangeStart = i;
                        recycleRangeCount = 1;
                    }
                    else
                    {
                        recycleRangeCount++;
                    }
                }
                else
                {
                    if (recycleRangeCount > 0)
                    {
                        GeneratorPosition position = new GeneratorPosition(recycleRangeStart, 0);
                        ((IRecyclingItemContainerGenerator)ItemContainerGenerator).Recycle(position, recycleRangeCount);
                        _realizedChildren.RemoveRange(recycleRangeStart, recycleRangeCount);

                        childCount -= recycleRangeCount;
                        i -= recycleRangeCount;
                        recycleRangeCount = 0;
                        recycleRangeStart = -1;
                    }
                }
            }

            if (recycleRangeCount > 0)
            {
                GeneratorPosition position = new GeneratorPosition(recycleRangeStart, 0);
                ((IRecyclingItemContainerGenerator)ItemContainerGenerator).Recycle(position, recycleRangeCount);
                _realizedChildren.RemoveRange(recycleRangeStart, recycleRangeCount);
            }
        }

        private bool IsInVisibleRange(int index)
        {
            List<Tuple<int, int>> ranges = GetVisibleRanges();

            return ranges.Where(r => Contains(r, index)).Any();
        }

        private List<Tuple<int, int>> GetVisibleRanges()
        {
            List<Tuple<int, int>> ranges = new List<Tuple<int, int>>();
            if (_firstVisibleIndex < 0)
            {
                ranges.Add(new Tuple<int, int>(ItemCount + _firstVisibleIndex, ItemCount - 1));
            }
            ranges.Add(new Tuple<int, int>(_firstVisibleIndex > 0 ? _firstVisibleIndex : 0,
                _lastVisibleIndex >= ItemCount ? ItemCount - 1 : _lastVisibleIndex));
            if (_lastVisibleIndex >= ItemCount)
            {
                ranges.Add(new Tuple<int, int>(0, _lastVisibleIndex - ItemCount));
            }
            return ranges;
        }

        private bool Contains(Tuple<int, int> tuple, int value)
        {
            return value >= tuple.Item1 && value <= tuple.Item2;
        }

        private void EnsureRealizedChildren()
        {
            if (_realizedChildren == null)
            {
                _realizedChildren = new List<UIElement>(Children.Count);

                for (int i = 0; i < Children.Count; i++)
                {
                    _realizedChildren.Add(Children[i]);
                }
            }
        }
        protected abstract int GetAlgorithmMaxDisplayedItems();
        private void CalculateIndices()
        {
            int layoutMax = GetAlgorithmMaxDisplayedItems();
            if (layoutMax == -1)
                return;

            double totalItems = (double)Math.Min(layoutMax, ItemCount);
            _consideredItems = totalItems;
            double halfTotal = 0.5 * totalItems;
            _firstVisibleIndex = (int)Math.Ceiling(_layoutOffset - halfTotal);
            _lastVisibleIndex = (int)Math.Floor(_layoutOffset + halfTotal);
            if (!_cyclicMode)
            {
                if (_firstVisibleIndex < 0)
                    _firstVisibleIndex = 0;
                if (_lastVisibleIndex >= ItemCount)
                    _lastVisibleIndex = ItemCount - 1;
            }
        }

    }
}
